
/* GCSx
** GUI.H
**
** Base gui classes and code
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#ifndef __GCSx_GUI_H_
#define __GCSx_GUI_H_

// Virtual base class for Window and Desktop
class WindowParent {
public:
    virtual int getScreenX() const = 0;
    virtual int getScreenY() const = 0;
    virtual void setChildDirty() = 0;
    virtual void childMoved(int fromX, int fromY, int toX, int toY, class Window* child) = 0;
    virtual void childResized(int fromW, int fromH, int toW, int toH, class Window* child) = 0;
    virtual void childDeleted(Window* child) = 0;
    virtual void childModified(Window* child) = 0; // Usage varies by child window type
    virtual void siblingModified(Window* sibling) = 0; // Usage varies by window type
    virtual ~WindowParent() = 0;
};

class Window : public virtual WindowParent {
protected:
    int x, y, width, height;
    WindowParent* parent; // Could be a Window, or desktop, or NULL
    WindowParent* parentNotify; // We notify this window if we are deleted- nothing more!
    // (how much of us is viewable based on parent window telling us- -1 if unknown)
    int viewWidth, viewHeight;
    
    // Unique ID across all windows
    int id;
    static int nextId;
    
    // Are we visible, and therefore need to update/redraw?
    // This is true if width and height are >0 and parent is non-NULL
    // and viewWidth/Height are non-zero
    int visible;
    
    int dirty; // If we need to redraw ourselves
    int childDirty; // If a child of ours needs to redraw
    // If we need to redraw entirely from scratch (only set by ::resize normally)
    // If a window doesn't check this (ie, if it normally repaints totally anyways)
    //   you don't need to zero it out. Many windows repaint totally, so this is
    //   usually ignored, but you can create a window that tracks partial repaints
    //   and this should then trigger a full repaint.
    int totalDirty; 

    // Default GUI routines only call this with total=1; your routines, if they
    // set partial dirties, need to track the partialness in your own variables
    void setDirty(int total = 0);

public:
    enum WindowType {
        // Window types- client windows are windows within a frame window
        // Widget windows are subwindows within a client window
        WINDOW_UNKNOWN,
        WINDOW_POPUPMENU,
        WINDOW_MENUBAR,
        WINDOW_FRAME,
        WINDOW_DIALOG,
        WINDOW_CLIENT,
        WINDOW_WIDGET,
        WINDOW_TOOLTIP,
        WINDOW_LASTTYPE = WINDOW_WIDGET,
    };

    enum WindowSort {
        // Window sort priorities
        WINDOWSORT_DESKTOP,
        WINDOWSORT_NORMAL,
        WINDOWSORT_ONTOP,
        WINDOWSORT_DOCKED,
        WINDOWSORT_MODAL,
        WINDOWSORT_POPUP,
    };
    
    // Whether to hide, disable, or enable a command in a popup menu
    enum CommandSupport {
        COMMAND_HIDE = 0,
        COMMAND_DISABLE = 1,
        COMMAND_ENABLE = 2,
        COMMAND_CHECKBOX = 4,
        COMMAND_RADIO = 8,
        COMMAND_SELECTED = 16,
    };

    Window();
    virtual ~Window();
    void setParent(WindowParent* wParent);
    void setParentNotify(WindowParent* wParentNotify);
    WindowParent* getParent() const { return parent; }
    
    void setChildDirty();
    // When a child resizes or moves, it calls one of these to tell us to
    // set appropriate dirtiness; the default versions of these do NOTHING
    virtual void childMoved(int fromX, int fromY, int toX, int toY, Window* child);
    virtual void childResized(int fromW, int fromH, int toW, int toH, Window* child);
    // Called when we are the parent or parentnotify of a child that is just about to be deleted
    virtual void childDeleted(Window* child);
    // Called by child windows when they "modify"- usage is up to the child windows
    // i.e. dialog widgets use this commonly; other windows may never use it
    // Default behavior propogates upward without modifying pointer (i.e. passes up
    // a grandchild pointer)
    virtual void childModified(Window* child);
    // Called by parent windows when a sibling was modified- usage is up to the
    // windows in question- mostly used by dialogs and frame wiondows
    // Default behavior is to do nothing
    virtual void siblingModified(Window* sibling);

    // Called when resolution changes; 0 for from if first resolution set
    virtual void resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp);
    
#ifndef NDEBUG
    virtual const char* debugDump() const;
#endif

    // x/y position includes any scrollbar-induced offset
    virtual void move(int xPos, int yPos);

    // (this function accepts zero/negative values as "hidden"- minimized- which
    //  unsets visible member))
    // fromParent is true if parent is telling us to resize and we don't need to
    // propogate it back (normally we'll tell our parent if we resize and our parent
    // is a container such as a frame window)
    // 'view' size is the amount of us that we can see, may be larger or smaller
    // than our actual width or height; -1 means unchanged; this is
    // used by frame windows and other containers to tell us that, if we want,
    // we can optimize our displays, resizes, and storage to this amount of surface
    virtual void resize(int newWidth, int newHeight, int newViewWidth = -1, int newViewHeight = -1, int fromParent = 0);

    // Returns true if closing is ok; false if close should abort (save dialog, for example)
    // This doesn't actually close the window- use an SDL_CLOSE event for that
    virtual int attemptClose();
    
    // Return true if event was used
    // Note that keyboard events typically -only- go to the window with focus
    // Mouse events typically -only- go to the window with mousefocus, and coordinates are adjusted
    // This function is NOT allowed to modify the event
    // Only SDL_CLOSE events are allowed to delete windows, and only children
    // (parent always deletes a window)
    // If you wish to close otherwise, issue an appropriate SDL_CLOSE event
    // Focus events should be careful if they also trigger focus changes, to prevent infinite loops
    // This function must not open a modal dialog or new event loop due to any of these events-
    //  SDL_QUIT, SDL_ACTIVEEVENT, SDL_VIDEORESIZE, SDL_VIDEOEXPOSE, SDL_IDLE
    // Any event that opens a modal dialog or event loop must be on an object that is thread-
    // safe for these events- (it will still receive these events while it's waiting on modal)
    //  SDL_ACTIVEEVENT, SDL_VIDEORESIZE, SDL_VIDEOEXPOSE, SDL_IDLE
    virtual int event(int hasFocus, const SDL_Event* event) = 0;
    
    // Do we need to be redrawn, or one of our children?
    int isDirty() const;
    
    // Return true if this paints using an alpha blend, IE windows below must be repainted first
    /* Not currently used or supported
    virtual int usesAlpha() const;
    */
    
    // Return true if this window wants to be deleted by it's parent when closed
    // If false, code elsewhere handles it's deletion (for example, config dialogs
    //   are static and global, and only deleted on exit)
    // This is used by Frame windows, Dialog windows, and the desktop, etc.
    // This defaults to TRUE
    virtual int wantsToBeDeleted() const;
    
    // Return true if this window is currently a docked part of the user interface
    // (such as a menu) This affects where other windows can move/resize to
    virtual int isDocked() const;
    
    // Return true if this window, when active, doesn't prevent the previous window
    // from appearing active; when inactive, returns to that previous window. (used
    // for popups and menus)
    virtual int tempFocus() const;
    
    // Return true if this window doesn't want any focus or events at all
    virtual int refuseAll() const;
    
    // Return whether this window supports the given SDL_COMMAND- used to disable
    // or hide menu options that aren't appropriate for the current menu
    virtual CommandSupport supportsCommand(int code) const;
    
    // Return type and sort of window
    // MUST return WINDOWSORT_MODAL if a modal window!
    // This can't be done via RTTI as there is not a one-to-one relationship to
    // classes (for example, menu class returns both memubar and popup types)
    virtual WindowType windowType() const;
    virtual WindowSort windowSort() const;
    
    // Display to surface (don't flip/update screen)
    // toDisplay is the rectangle that MUST be updated per parent/desktop request
    // and is gaurunteed to be within your x/w/size; clipArea is your clipping area
    // and may extend further than your size and will always contain toDisplay
    // Always set a clip rectangle on destSurface before drawing- either clipArea
    // or a subrectangle of clipArea
    // offset tells you what coordinate is your 0/0
    // toDisplay and clipArea are fully x/y based (not 0-based)
    // If no required update, toDisplay will have width of zero
    // display() must store the actual updated area back into toDisplay, which
    // should have been clipped by clipArea
    virtual void display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) = 0;
    
    // Return true if this window or it's parent is currently part of the desktop
    int isOnDesktop() const;
    
    // Closes this window or it's parent via an SDL_CLOSE event
    // Does NOT call attemptClose() if you set skipAttempt true
    void closeWindow(int skipAttempt = 0);
    
    // Returns pointer to tooltip to display; x/y relative to window
    // Return NULL for none
    virtual const char* tooltip(int xPos, int yPos) const;
    
    // Are coordinates within our boundaries?
    int isCoords(int xPos, int yPos) const;
    int getX() const;
    int getY() const;
    int getWidth() const;
    int getHeight() const;
    int getId() const;
    void getRect(Rect& rect) const;
    
    // Gets actual on-screen position
    int getScreenX() const;
    int getScreenY() const;
    
    // Receives undo/redo notifications, if appropriate
    virtual void undoNotify(int undoType, int undoItemId, int undoItemSubId, int uX, int uY, int uW, int uH);
};

// Used to hold and organize windows, for a desktop, or within another window
// Doesn't contain any actual code to display the windows to screen
class WindowCollection : public virtual WindowParent {
protected:
    // (internal gui data regarding windows and focus)
    // List so that iterators work even if elements change
    std::list<Window*> windowDataDisplay; // Windows in display order
    std::list<Window*> windowDataFocus; // Windows in focus order
    Window* currentFocus;
    Window* previousFocus; // (last window with focus when a tempFocus window is active)
    Window* lastFocus; // (window with input focus when we lost our own focus, ie app change)
    Window* currentMouseFocus;
    int lastMouseX;
    int lastMouseY;
    
    // Allow clicking outside any windows?
    int allowDesktopClick;
    
    // (if a modal window is defined, no window below it can accept events or focus)
    Window* modalPos;

    // What we need to update next refresh
    Rect updateRect;
    
    // How large our "canvas" is for windows- after subtracting docked windows and
    // menus. This is only used by child classes that care, such as Desktop.
    int overallWidth;
    int overallHeight;
    int canvasX;
    int canvasY;
    int canvasWidth;
    int canvasHeight;

    int windowRunEvent(Window* target, const SDL_Event* event);
    const Window* findWindowByCoords(int x, int y) const;
    Window* findWindowByCoords(int x, int y);
    // (sendMoveEvent sends a custom-generated mouse movement event to
    //  reconfirm mouse position if focus has changed)
    Window* recheckMouseFocus(int sendMoveEvent = 0);
    // (toTop should always be true except when called from bringToTop)
    // Returns true if a focus change was made
    int changeInputFocus(Window* window, int toTop = 1);
    
    // Scans all windows for docked windows and adjusts our canvas, if we're
    // using that feature
    void adjustCanvas();
    
    // Used to handle broadcast-type events, mostly SDL_COMMAND, SDL_IDLE
    int (*handleEvents)(const SDL_Event* event);

    // Add something to our refresh rect
    void addToUpdateRect(const Rect& add);

public:
    WindowCollection();
    virtual ~WindowCollection();

    // Called when resolution changes; 0 for from if first resolution set
    void resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp);

    // Sets the function to catch SDL_COMMAND, SDL_IDLE, and other broadcast events
    // Not called on SDL_COMMAND if another window uses it
    void setEventHandler(int (*eventHandler)(const SDL_Event* event));

    // Add or remove Window from the collection (adds to top of z-order for the window's sort type)
    // Note that if addWindow fails, window will not be added, so it won't ever be closed and delete itself
    void addWindow(Window* item, int giveFocus = 1);
    // (this MUST BE CALLED to remove a window that's been deleted)
    void removeWindow(Window* item);

    // pos = 0 for first, 1 for second, etc.
    // Scans in focus order, returning lowest window first
    Window* findWindow(Window::WindowType type, int pos);

    // Verifies that a specific window is on desktop
    int verifyWindow(const Window* target) const;
    Window* verifyWindow(int targetId);
    
    // returns window with input focus
    Window* findFocusWindow();

    // returns window with previous input focus, or current if no previous
    Window* findPreviousFocusWindow();

    // Bring to top or bottom (item must already be part of collection)
    void bringToTop(Window* item, int giveFocus = 1);
    void sendToBottom(Window* item); // Loses focus unless only window
    
    // Handles an untouched event and propogates it to the proper
    // window or windows. Returns true if event was used. After this function,
    // any window pointers could poitn to closed windows.
    int handleEvent(const SDL_Event* event);
};

class Desktop : public WindowCollection {
protected:
    // Quit state is global to all event loops- once a quit is requested, all
    // loops above and below will quit.
    int quit; // Any non-zero value is a quit, and can represent a "quit type"
    int preQuit;
    int cancelPreQuit;
    int eventLoopCount;
    int modalReturn;
    int eventCleanup; // Ignore mouse/keyboard events until queue is clean
    
    // If a child of ours needs redrawing
    int childDirty;
    
    // Location and time of last left mouse click, to check for double-click
    int dclickX;
    int dclickY;
    int dclickTicks;
    
    enum {
        // Double-click sensitivity, in time (ms) and distance (max pixels)
        DOUBLECLICK_TIME = 1000,
        DOUBLECLICK_DIST = 2,
        
        // Phase rotation schedule
        DELAY_PHASE_ROTATE = 60,

        // Tooltip delay
        DELAY_TOOLTIP_SHOW = 1000,

        // Short and long timeouts (long repeats)
        DELAY_IDLE_SHORT = 3000,
        DELAY_IDLE_LONG = 60000,
    };
    
    // Global phase for selection rectangles and glowing cursors
    int cursorPhase;
    int cursorAlpha;
    int selectionPhase;
    Uint32 selectionPhaseMs;
    
    // Idle timeout
    Uint32 idleTimeout;
    int didShortTimeout;
    int didLongTimeout;
    
    // Current tooltip, time until tooltip should show
    class ToolTip* tooltip;
    Uint32 tooltipMs;
    
    // Keypad-to-regular sym conversion
    static SDLKey keypadConvert[10];
    
    // Background
    SDL_Surface* backgroundImage;
    int bkSrcX;
    int bkSrcY;
    int bkSrcW;
    int bkSrcH;
    int bkDestX;
    int bkDestY;
    int backgroundColorIndex;
    int backgroundAlpha;
    
    // Game loop (optional)
    void (*gameLoopH)(int);

    // Global function for dynamic popups
    Window::CommandSupport (*supportsCommandH)(int code);

public:
    int lastShortcutKey;

    Desktop();
    virtual ~Desktop();
    
    void setBackground(SDL_Surface* image, int colorIndex);
    void setBackgroundAlpha(int alpha); // openGL only, 0-256
    // (also enables desktop clicking)
    void setGameLoop(void (*gameLoop)(int));

    int currentSelectionPhase() const { return selectionPhase; }
    int currentCursorAlpha() const { return cursorAlpha; }

    void setChildDirty();
    void childMoved(int fromX, int fromY, int toX, int toY, Window* child);
    void childResized(int fromW, int fromH, int toW, int toH, Window* child);
    void childDeleted(Window* child);
    void childModified(Window* child);
    void siblingModified(Window* sibling);

    // Broadcast a custom event
    // For example, broadcastEvent(SDL_COMMAND, command_code);
    // Can pass a single exempt window which will NOT receive the event
    // runImmediate recurses it to event loop immediately, and returns result-
    //   intended for use by desktop event handler only
    // Otherwise returns 0
    int broadcastEvent(int type, int code, void* data = NULL, Window* exempt = NULL, int runImmediate = 0);
    // SDL_OBJECTCHANGE events always go out *IMMEDIATELY* (interrupting current work)
    // @TODO: all uses of this should pass (and expect) the base (OR EDIT?) form of the object
    //        for exampe, Scene and Layer, not SceneEdit or LayerEdit
    void broadcastObjChange(int code, void* object, int data1, int data2, Window* exempt = NULL);

    // Send a focus or close event to a specific window
    // Cancels sending anything if window is NULL
    // For example, focusEvent(SDL_MOUSEFOCUS, 1, window);
    // Only intended for focus/close events
    // This is the correct and ONLY way for a window to request a window,
    // including itself, to close (use SDL_CLOSE)
    // Note that SDL_CLOSE events always go to the very front of the queue, so
    // no other events will process first, for safety
    void focusEvent(int type, int code, Window* window);

    // Main draw/event loop; also used for modal dialogs
    // Returns value returned by a modal dialog, 0 if none was returned (closed by mistake?)
    // Main event loop always returns 0
    int eventLoop();
    
    // Updates screen (use during loops to modify onscreen without using threads)
    void updateScreen();
    
    // Ignore all keyboard/mouse events (ie user interaction) until event queue clears
    // Good if you lock things up for a short period, to prevent invalid clicks
    void initEventCleanup();
    
    // Closes and clears all windows off the desktop immediately
    void closeAllWindows();

    // Deletes all windows off the desktop, no user prompts, no events
    void deleteAllWindows();
    
    // Call during a modal dialog upon closing to tell inner event loop what value to return
    void setModalReturn(int returnValue);
    
    // Call during a SDL_PREQUIT event to cancel the quit
    void cancelQuit();
    
    // Call to find out if we're cancelling a quit; use this so that you
    // stop propogating a SDL_PREQUIT event once it's been cancelled
    int cancelQuitState();
    
    // Sets the function to check global support for a popup command
    // Presumably, if this function returns "supported", the global event handler will handle it
    void setSupportsHandler(Window::CommandSupport (*supportsHandler)(int code));
    
    // Call global support handler; pass prior response, this will combine them
    Window::CommandSupport supportsCommand(int code, Window::CommandSupport prior) const;

    // Desktop size relative to entire screen, MINUS docked windows
    int desktopX() const;
    int desktopY() const;
    int desktopWidth() const;
    int desktopHeight() const;

    int getScreenX() const;
    int getScreenY() const;
    
    // Called when resolution changes; 0 for from if first resolution set
    void resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp);
    
    // Cancels any possibility of a double click- use when initiating
    // "fake" clicks or other situations where double clicks are not desired
    void preventDoubleClick();
};

// Used for SDL_OBJECTCHANGE events
struct ObjChange {
    const void* obj;
    int info1;
    int info2;
};

// Custom event types
enum {
    // Lose or gain mouse focus- code 0 lose 1 gain, data1 window
    // code &2 if parent FRAME window still has focus, just on another child
    // (may later expand &2 functionality to dialogs)
    SDL_MOUSEFOCUS = SDL_USEREVENT,
    // Lose or gain input focus- code 0 lose 1 gain, data1 window
    // code &2 if parent FRAME window still has focus, just on another child
    // (may later expand &2 functionality to dialogs)
    SDL_INPUTFOCUS,
    // Command (from a menu or accelerator) code in 'code' member
    SDL_COMMAND,
    // Close window, data1 window
    // (not a request, but a demand- any pre-close should have already ran)
    SDL_CLOSE,
    // Pre-quit, idle, etc- sub event in 'code'
    SDL_SPECIAL,
    // Broadcast/system key
    SDL_SYSKEY,
    // Double-click left mouse button- otherwise same as SDL_MOUSEBUTTONDOWN
    SDL_MOUSEBUTTONDBL,
    // Called when various game objects update, to tell needed windows to update
    // code contains one OBJ_ and one or more OBJMOD_ constants OR'd together
    // data1 points to a ObjChange structure
    SDL_OBJECTCHANGE,

    // Sub events for SDL_SPECIAL- stored in 'code'
    // Pre-quit, can be cancelled (prompt for saving, etc.)
    SDL_PREQUIT = 1,
    // Close game
    SDL_CLOSEGAME = 2,
    // Desktop now 'active' or not 'active' window (if enabled during gameplay mode)
    SDL_DESKTOPACTIVE = 3,
    SDL_DESKTOPINACTIVE = 4,
    // Special types of quits
    SDL_SPECIALQUIT = 64, // Set at 64 so you can go & SDL_SPECIALQUIT
    SDL_QUITTOEDITOR = 65,
    SDL_QUITTOFRONT = 66,
    SDL_QUITTOGAME = 67,
    // Idle- set at 128 so you can go & SDL_IDLE to check for this or any
    //       idle-derived event like IDLEPHASE
    SDL_IDLE = 128,
    // Idle with cursor glow and selection phase change
    SDL_IDLEPHASE = 129,
    // Idle with cursor glow
    SDL_IDLECURSOR = 130,
    // Idle short timeout (no activity for 3 sec, as defined in enum)
    SDL_IDLETIMEOUTSHORT = 131,
    // Idle long timeout (no activity every 60 sec, as defined in enum)
    SDL_IDLETIMEOUTLONG = 132,
};

// The main desktop
extern Desktop* desktop;

// Combines an SDLKey and either KMOD_CTRL, KMOD_SHIFT, KMOD_ALT, or a combo
// to a single value
#define combineKey(sym, mod) (((mod) << 16) | (sym))

#endif

